#include "cmus-rg.h"

void init_cmus_rg(TTF_Font **font, bool *render_change, cr_track **track, cr_player **player, int *frame) {
    *frame = 0;

    /* calculate max line width */
    int w;
    if (TTF_SizeUTF8(*font, "W", &w, NULL))
        fprintf(stderr, "TTF_SizeUTF8 error: %s\n", SDL_GetError());

    set_render_change(render_change);
    
    *track = create_cr_track((uint64_t) 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, (uint64_t) 0, (uint64_t) 0, NULL);
    *player = create_cr_player(NULL, NULL, (uint64_t) 0, (uint64_t) 0, (uint64_t) 0, false, false, false);
}

void cleanup_cmus_rg(cr_track *track, cr_player *player) {
    destroy_cr_track(track);
    destroy_cr_player(player);
}

void update_cmus_rg(cr_state *state) {
    /* lazily render (at least every 10th frame) */
    if (state->frame % 10 == 0)
        set_render_change(&state->render_change);

    if (state->render_change) {
        /* query processing */
        /* update this even lazier, as it involves subprocess costs */
        if (state->render_change || state->frame % 60 == 0)
            process_info(state->update, state->track, state->player, &state->previous_info_hash, &state->incomplete_parse);
 
        /* query all drawables for updates and update them if need be */
        for (int i = 0; i < TOKEN_ENUM_SIZE; i++) {
            if (state->update[i]) {
                state->update[i] = false;
                update_graphics_state(i, &(state->drawables[i]), &(state->drawextras[i]), state);
            }
        }
    }

    /* frame logic */
    state->frame++;
    if (state->frame >= 60)
        state->frame %= 60;
}

FILE* query_cmus() {
    FILE *fp = NULL;

    /* open command for reading */
    fp = popen("cmus-remote -Q 2>&1", "r");
    if (fp == NULL) {
        fprintf(stderr, "cmus-remote error: command not found or failed to run\n");
        exit(EXIT_FAILURE);
    }

    return fp;
}

void process_info(bool *update, cr_track *track, cr_player *player, uint64_t *previous_info_hash, bool *incomplete_parse) {
    /* buffer */
    size_t max = 1024;
    char output[max];

    /* query data */
    FILE *fp = query_cmus();

    uint64_t info_hash = 0;
    /* read output one line at a time */
    while (fgets(output, sizeof(output) - 1, fp) != NULL) {
        info_hash += hash(output);

        // TODO: write streaming parser lol
        fill_cr_track_or_cr_player(update, track, player, output, max, incomplete_parse);
    }

    /* close once done! */
    pclose(fp);

    /* skip doing anything if info hasn't changed */
    /* NOTE: currently unused, replaced by another subsystem */
    if (*previous_info_hash == info_hash)
        return;

    *previous_info_hash = info_hash;

    if (DEBUG) {
        print_cr_track(track);
        print_cr_player(player);
    }
}

void update_graphics_state(int token, cr_drawable *drawable, cr_drawable *drawextra, cr_state *state) {
    switch (token) {
        case TOKEN_FILE: {
            unsigned char *image = NULL;
            int image_size = 0;
            /* read id3 */
            read_image(state->track->file, &image, &image_size);
            /* proceed to draw */
            register_cr_drawable(
                drawable,
                drawable_cover_art(
                    state->renderer,
                    /* image buffer and it's size */
                    image, image_size,
                    state->window_w, state->window_h
                ),
                &state->render_change
            );
            break;
        }

        case TOKEN_ALBUM_TAG: {
            uint32_t color = 0x111111dd;
            register_cr_drawable(
                drawextra,
                drawable_text(
                    /* renderer and font */
                    &state->renderer, &state->font, 
                    /* x and y */
                    state->border, -state->border - state->font_height,
                    /* width and height */
                    state->window_w, state->window_h,
                    /* string and color */
                    state->track->album_tag, color
                ),
                &state->render_change
            );
            register_cr_drawable(
                drawable,
                drawable_bg_rect(&state->renderer, drawextra->rect, 0xffffffdd - color + 0x00000088),
                &state->render_change
            );
            break;
        }

        case TOKEN_ARTIST_TAG: {
            uint32_t color = 0x111111dd;
            register_cr_drawable(
                drawextra,
                drawable_text(
                    &state->renderer, &state->font,
                    state->border, -state->border - 2 * state->font_height,
                    state->window_w, state->window_h,
                    state->track->artist_tag, color
                ),
                &state->render_change
            );
            register_cr_drawable(
                drawable,
                drawable_bg_rect(&state->renderer, drawextra->rect, 0xffffffdd - color + 0x00000088),
                &state->render_change
            );
            break;
        }

        case TOKEN_TITLE_TAG: {
            uint32_t color = 0x111111dd;
            register_cr_drawable(
                drawextra,
                drawable_text(
                    &state->renderer, &state->font,
                    state->border, -state->border,
                    state->window_w, state->window_h,
                    state->track->title_tag, color
                ),
                &state->render_change
            );
            register_cr_drawable(
                drawable,
                drawable_bg_rect(&state->renderer, drawextra->rect, 0xffffffdd - color + 0x00000088),
                &state->render_change
            );
            break;
        }

        case TOKEN_STREAM: {
            /* don't overdraw the album tag if it is set */
            /* but allow the stream texture to be destroyed if it isn't yet */
            if (state->track->album_tag != NULL && state->drawables[TOKEN_STREAM].texture == NULL)
                break;

            uint32_t color = 0x111111dd;
            register_cr_drawable(
                drawextra,
                drawable_text(
                    &state->renderer, &state->font,
                    state->border, -state->border - state->font_height,
                    state->window_w, state->window_h,
                    state->track->stream, color
                ),
                &state->render_change
            );
            register_cr_drawable(
                drawable,
                drawable_bg_rect(&state->renderer, drawextra->rect, 0xffffffdd - color + 0x00000088),
                &state->render_change
            );
            break;
        }

        case TOKEN_AAA_MODE: {
            uint32_t color = 0x111111dd;
            unsigned int str_len = 7 + strlen(state->player->aaa_mode);
            char str[str_len];
            memset(str, 0, str_len);
            strncpy(str, "mode: ", 6);
            strncpy(str + 6, state->player->aaa_mode, str_len - 7);
            register_cr_drawable(
                drawextra,
                drawable_text(
                    &state->renderer, &state->font,
                    state->border, state->border + state->font_height,
                    state->window_w, state->window_h,
                    str, color
                ),
                &state->render_change
            );
            register_cr_drawable(
                drawable,
                drawable_bg_rect(&state->renderer, drawextra->rect, 0xffffffdd - color + 0x00000088),
                &state->render_change
            );

            /* update dependend buttons */
            /* reassign status button area after texture change */
            cr_drawable empty[2];
            empty[0] = empty_drawable();
            empty[1] = empty_drawable();
            update_button(state->root, BUTTON_TOGGLE_AAA_MODE, drawextra->rect.x, drawextra->rect.y, drawextra->rect.w, drawextra->rect.h, NULL, empty, true, false, false);
            break;
        }

        case TOKEN_STATUS: {
            /* update toggle status information */
            uint32_t color = 0x111111dd;
            register_cr_drawable(
                drawextra,
                drawable_text(
                    &state->renderer, &state->font,
                    state->border, state->border,
                    state->window_w, state->window_h,
                    state->player->status, color
                ),
                &state->render_change
            );
            register_cr_drawable(
                drawable,
                drawable_bg_rect(&state->renderer, drawextra->rect, 0xffffffdd - color + 0x00000088),
                &state->render_change
            );

            /* update dependend buttons */
            /* reassign status button area after texture change */
            cr_drawable empty[2];
            empty[0] = empty_drawable();
            empty[1] = empty_drawable();
            update_button(state->root, BUTTON_TOGGLE_STATUS, drawextra->rect.x, drawextra->rect.y, drawextra->rect.w, drawextra->rect.h, NULL, empty, true, false, false);

            /* reassign next button area after texture change */
            unsigned int prev_x = drawextra->rect.x + drawextra->rect.w + state->border * 0.2;
            cr_drawable prev[2];
            prev[1] = drawable_text(&state->renderer, &state->font, prev_x, drawextra->rect.y, state->window_w, state->window_h, "←", color);
            prev[0] = drawable_bg_rect(&state->renderer, prev[1].rect, 0xffffffdd - color + 0x00000088),
            update_button(state->root, BUTTON_PREV, prev_x, drawextra->rect.y, prev[1].rect.w, prev[1].rect.h, NULL, prev, true, false, true);

            /* reassign prev button area after texture change */
            unsigned int next_x = prev_x + prev[1].rect.w + state->border * 0.2;
            cr_drawable next[2];
            next[1] = drawable_text(&state->renderer, &state->font, next_x, drawextra->rect.y, state->window_w, state->window_h, "→", color);
            next[0] = drawable_bg_rect(&state->renderer, next[1].rect, 0xffffffdd - color + 0x00000088),
            update_button(state->root, BUTTON_NEXT, next_x, drawextra->rect.y, next[1].rect.w, next[1].rect.h, NULL, next, true, false, true);
            break;
        }

        case TOKEN_POSITION: {
            uint32_t color = 0xeeeeeedd;
            if (state->track->duration != -1 && state->player->position < state->track->duration) {
                /* doesn't support hour long songs lol */
                char time_str[3 + 2 * 5];
                sprintf(
                    time_str,
                    "%.2d:%.2d / %.2d:%.2d",
                    /* minutes, keep it 2 digits */
                    ((state->player->position / 60) % 100),
                    (state->player->position % 60),
                    /* minutes, keep it 2 digits */
                    ((state->track->duration / 60) % 100),
                    (state->track->duration % 60)
                );

                register_cr_drawable(
                    drawextra,
                    drawable_text(
                        &state->renderer, &state->font,
                        -state->border, state->border,
                        state->window_w, state->window_h,
                        time_str, color
                    ),
                    &state->render_change
                );
                register_cr_drawable(
                    drawable,
                    drawable_bg_rect(&state->renderer, drawextra->rect, 0xffffffdd - color + 0x00000088),
                    &state->render_change
                );
            } else {
                /* still doesn't support hour long songs lol */
                char time_str[1 * 5];
                sprintf(
                    time_str,
                    "%.2d:%.2d",
                    /* minutes, keep it 2 digits */
                    ((state->player->position / 60) % 100),
                    (state->player->position % 60)
                );

                register_cr_drawable(
                    drawextra,
                    drawable_text(
                        &state->renderer, &state->font,
                        -state->border, state->border,
                        state->window_w, state->window_h,
                        time_str, color
                    ),
                    &state->render_change
                );
                register_cr_drawable(
                    drawable,
                    drawable_bg_rect(&state->renderer, drawextra->rect, 0xffffffdd - color + 0x00000088),
                    &state->render_change
                );
            }
            break;
        }

        default:
            break;
    }
}
